/***************************************************************************
 *   Copyright (C) 2009 by Kbranch                                         *
 *   kbranch@kbranch.net                                                   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#define MAINFILE

#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include <math.h>
#include <time.h>
#include <string.h>
#include "common.h"

using namespace std;

int main(int argc, char *argv[])
{
	time_t startTime = 0;
	time_t endTime = 0;
	int64 loopCount = 0;
	int quiet = 0;
	int statAmount = 0;
	int seedOffset = 0;
	char stat[40];
	double lastTotalDamage = 0;
	
	// Extra stat amounts - passed as command line arguments to help the frontend determine stat weights
	agiOffset = 0;
	hasteOffset = 0;
	arPenOffset = 0;
	hitOffset = 0;
	expertiseOffset = 0;
	critOffset = 0;
	apOffset = 0;
	weaponAPOffset = 0;
	strOffset = 0;
	
	if(argc > 1)
		quiet = atoi(argv[1]);
	
	// Parses command line arguments for stat offsets
	if(argc == 4)
	{
		strcpy(stat, argv[2]);
		statAmount = atoi(argv[3]);
		
		if(!strcmp(stat, "Agi"))
			agiOffset = statAmount;
		else if(!strcmp(stat, "HasteRating"))
			hasteOffset = statAmount;
		else if(!strcmp(stat, "ArPenRating"))
			arPenOffset = statAmount;
		else if(!strcmp(stat, "HitRating"))
			hitOffset = statAmount;
		else if(!strcmp(stat, "CritRating"))
			critOffset = statAmount;
		else if(!strcmp(stat, "AP"))
			apOffset = statAmount * 2;
		else if(!strcmp(stat, "WeaponAP"))
		{
			apOffset = statAmount * 2;
			weaponAPOffset = statAmount * 2;
		}
		else if(!strcmp(stat, "Str"))
			strOffset = statAmount;
	}
	
	if(argc == 5)
		seedOffset = atoi(argv[4]);
	
	MTRand_closed rand(time(NULL) + seedOffset);
	
	Init();
	CalculateStats();
	CalculateAverageDamage();
	
	float fightDamage[numFights + 1];
	double energyTickRateInverse = 1.0 / energyTickRate;
	startTime = time(NULL);
	
	#ifndef DEBUG
	if(!quiet)
		cout << "Progress: 0% <                                                  >" << '\r' << "Progress: 0% <" << flush;
	#endif
	
	for(int i = 1; i <= numFights; i++)
	{
		int64 nextSwing = 0;
		lowestBuffTime = fightLength;
		
		currentTime = 0;
		energy = maxEnergy;
		comboPoints = 0;
		nextAttack.name = -1;
		nextAttack.time = -1;
		lastEnergyTick = 0;
		lastGCDAttackTime = 0 - gcd;
		omenActive = 0;
		numActiveBuffs = 0;
		
		for(int j = 0; j < numBuffs; j++)
		{
			buffs[j].active = 0;
			buffs[j].lastTrigger = 0 - 10000 * PRECISION;
			buffs[j].endTime = -1;
			buffs[j].lastTick = 0 - fightLength;
			buffs[j].nextTick = 0 - fightLength;
			buffs[j].lastFaded = 0;
			buffs[j].nextCooldown = 0;
		}
		
		buffs[TRINKET1].nextCooldown = trinket1Delay;
		buffs[TRINKET2].nextCooldown = trinket2Delay;
		
		if(mangleBot)
		{
			buffs[MANGLE_B].active = 1;
			buffs[MANGLE_B].lastTrigger = 0;
			buffs[MANGLE_B].duration = fightLength;
			buffs[MANGLE_B].endTime = fightLength;
			
			activeBuffs[numActiveBuffs] = MANGLE_B;
			numActiveBuffs++;
		}
		
		CalculateStats();
		CalculateAverageDamage();
		ChooseNextAttack();
		
		while(currentTime < fightLength)
		{
			int numTicks = (currentTime - lastEnergyTick) * energyTickRateInverse;
			
			energy += numTicks;
			lastEnergyTick += numTicks * energyTickRate;
			
			if(energy > maxEnergy)
			{
				#ifdef DEBUG
				cout << "Energy wasted at " << currentTime / double(PRECISION) << endl;
				#endif
				
				energy = maxEnergy;
			}
			
			if(buffs[HEROISM].enabled && !buffs[HEROISM].active && currentTime >= buffs[HEROISM].nextCooldown)
			{
				buffs[HEROISM].active = 1;
				buffs[HEROISM].lastTrigger = currentTime;
				buffs[HEROISM].endTime = currentTime + buffs[HEROISM].duration;
				buffs[HEROISM].nextCooldown = currentTime + buffs[HEROISM].cooldown;
				
				activeBuffs[numActiveBuffs] = HEROISM;
				numActiveBuffs++;
				
				currentStats += buffs[HEROISM].stats;
				
				#ifdef DEBUG
				cout << "Heroism used at " << currentTime / double(PRECISION) << endl;
				#endif
			}
			
			// Checks RIP and RAKE timers for bleed ticks
			for(int j = RAKE_A; j <= RIP_A; j++)
			{
				if(buffs[j].active && currentTime >= buffs[j].nextTick)
				{
					double roll;
					
					if(j == RIP_A && talents.primalGore)
						roll = rand();
					
					if(j == RIP_A && roll <= currentStats.GetCritChance() + fourT9 * 0.05 && talents.primalGore) // Attack crits
					{
						attacks[j].tickCrits++;
						attacks[j].damageDealt += buffs[j].avgTick * critMod * currentStats.GetModifier(BLEEDDAMAGEMOD);
						
						#ifdef DEBUG
						cout << "Bleed " << names[j] << " tick crits for " << buffs[j].avgTick * critMod * currentStats.GetModifier(BLEEDDAMAGEMOD) << " at " << currentTime / double(PRECISION) << ", isBleeding: " << isBleeding << endl;
						#endif
						
						CheckProcs(TICK, CRIT, j, rand);
					}
					else	// Attack hits
					{
						attacks[j].tickHits++;
						attacks[j].damageDealt += buffs[j].avgTick * currentStats.GetModifier(BLEEDDAMAGEMOD);
						
						#ifdef DEBUG
						cout << "Bleed " << names[j] << " ticks for " << buffs[j].avgTick * currentStats.GetModifier(BLEEDDAMAGEMOD) << " at " << currentTime / double(PRECISION) << ", isBleeding: " << isBleeding << endl;
						#endif
						
						CheckProcs(TICK, HIT, j, rand);
					}
					
					buffs[j].lastTick = currentTime;
					buffs[j].nextTick = currentTime + buffs[j].tickFrequency;
				}
			}
			
			if(nextSwing <= currentTime)
			{
				Swing(rand);
				nextSwing = currentTime + currentStats.GetAttackSpeed();
			}
			
			if(currentTime >= nextAttack.time && nextAttack.name != -1)
			{
				DoNextAttack(rand);
				lowestBuffTime = GetLowestBuff();
			}
			
			// Finds when the next event will happen
			int64 lowestTime;
			
			if(lowestBuffTime == currentTime || lowestBuffTime == fightLength)
			{
				CheckBuffDurations();
				lowestBuffTime = GetLowestBuff();
			}
			
			lowestTime = lowestBuffTime;
			
			if(nextSwing < lowestTime)
				lowestTime = nextSwing;
			if(nextAttack.time < lowestTime && nextAttack.time > currentTime)
				lowestTime = nextAttack.time;
			
			currentTime = lowestTime;
			loopCount++;
		}
		
		// Checks for buff downtime at the end of the fight
		for(int j = 0; j < numAttacks; j++)
		{
			if(!buffs[j].active && buffs[j].type == USE)
			{
				buffs[j].downTime += fightLength - buffs[j].lastFaded;
				
				#ifdef DEBUG
				cout << (fightLength - buffs[j].lastFaded) / double(PRECISION) << " downtime added for " << names[j] << endl;
				#endif
			}
		}
		
		double totalDamage = 0;
		for(int j = 0; j < numAttacks; j++)
			totalDamage += attacks[j].damageDealt;
		
		fightDamage[i] = totalDamage - lastTotalDamage;
		lastTotalDamage = totalDamage;
		
		#ifndef DEBUG
		if(!quiet && numFights >= 50 && i % (numFights / 50) == 0)
		{
			cout << "\rProgress: " << int(i / double(numFights) * 100) << "% <                                                  >" << "\rProgress: " << int(i / double(numFights) * 100) << "% <";
			
			for(int j = 0; j < i / (numFights / 50); j++)
			{
				cout << "=";
			}
			
			cout << flush;
		}
		#endif
	}
	
	#ifndef DEBUG
	if(!quiet)
	{
		cout << "\r";
		
		for(int i = 0; i < 75; i++)
			cout << ' ';
		
		cout << '\r';
	}
	#endif
	
	endTime = time(NULL);
	
	// Resets temporary buffs so the report has accurate stats
	for(int j = 0; j < numBuffs; j++)
	{
		buffs[j].active = 0;
		buffs[j].lastTrigger = 0 - fightLength;
		buffs[j].endTime = -1;
		buffs[j].lastTick = 0 - fightLength;
		numActiveBuffs = 0;
	}
	
	CalculateStats();
	
// 	cout << loopCount / double(numFights) << endl;
	
	// Prints either the full report, or just the average DPS if in quiet mode
	if(!quiet)
	{
		PrintReport(endTime - startTime, fightDamage);
	}
	else
	{
		double total[numAttacks];
		double totalDamage = 0;
		
		for(int i = 0; i < numAttacks; i++)
		{
			total[i] = attacks[i].hits + attacks[i].glances + attacks[i].crits + attacks[i].misses;
			totalDamage += attacks[i].damageDealt;
		}
		
		cout << double(totalDamage) / (double(fightLength) / double(PRECISION) * numFights) << endl;
	}
	
	#ifdef WIN32
	if(!quiet)
	{
		cout << endl << "Press enter to continue." << endl;
		cin.get();
	}
	#endif
	
	return EXIT_SUCCESS;
}

// Returns the amount of time until 'cost' energy will be available
inline int64 TimeUntilEnergy(int cost)
{
	if(energy >= cost)
		return 0;
	
	return (cost - energy) * energyTickRate - (currentTime - lastEnergyTick);
}

// Decides and queues up the next attack
inline void ChooseNextAttack()
{
	if(omenActive && buffs[ROAR_B].active && isBleeding && buffs[MANGLE_B].active && nextAttack.name != SHRED) // Always shred if OOC and required buffs are up
		nextAttack = CreateAttack(SHRED);
	else if(nextAttack.name == -1 && energy >= 60 && buffs[BERSERK_B].enabled && currentTime >= buffs[BERSERK_B].nextCooldown && buffs[MANGLE_B].active && buffs[ROAR_B].active && isBleeding)  // Use Berserk if conditions are right
		nextAttack = CreateAttack(BERSERK_A);
	else if(nextAttack.name == -1)
	{
		int timeToEnergyCap = TimeUntilEnergy(maxEnergy) - latency;
		int lowestBuff = -1;
		int lowestBuffTime = 60 * PRECISION;
		
		// Get the time left for Mangle, Rake, Rip, and Roar
		for(int i = MANGLE_B; i <= ROAR_B; i++)
		{
			if(buffs[i].active)
			{
				buffs[i].timeLeft = buffs[i].endTime - currentTime;
				
				if(buffs[i].timeLeft < lowestBuffTime)
				{
					lowestBuff = i;
					lowestBuffTime = buffs[i].timeLeft;
				}
			}
			else
			{
				buffs[i].timeLeft = 0;
				
				if(lowestBuffTime > 0)
				{
					lowestBuff = i;
					lowestBuffTime = 0;
				}
			}
		}
		
		#ifdef DEBUG
		cout << "Lowest buff: " << names[lowestBuff] << ", " << buffs[lowestBuff].timeLeft / double(PRECISION) << " seconds left at " << currentTime / double(PRECISION) << endl;
		#endif
		
		if(!buffs[BERSERK_B].active)
		{
			if(!buffs[ROAR_B].active)
			{
				if(comboPoints > 0)
					nextAttack = CreateAttack(ROAR_A);
				else
					nextAttack = CreateAttack(MANGLE_A);
			}
			else
			{
				if(comboPoints == 5)
				{
					if((lowestBuff == RIP_B || !buffs[RIP_B].active) && buffs[RIP_B].timeLeft < timeToEnergyCap)
						nextAttack = CreateAttack(RIP_A, lowestBuffTime + currentTime);
					else if(CanBite())
						nextAttack = CreateAttack(BITE);
				}
				
				if(nextAttack.name == -1)
				{
					if(comboPoints >= minRoarPoints && lowestBuff == ROAR_B && buffs[ROAR_B].timeLeft <= maxRoarClip)
						nextAttack = CreateAttack(ROAR_A);
					else if(lowestBuff == MANGLE_B && lowestBuffTime < timeToEnergyCap)
						nextAttack = CreateAttack(MANGLE_A, lowestBuffTime + currentTime);
					else if(lowestBuff == RAKE_B && lowestBuffTime < timeToEnergyCap)
						nextAttack = CreateAttack(RAKE_A, lowestBuffTime + currentTime);
					else
					{
						if(useShred)
							nextAttack = CreateAttack(SHRED);
						else
							nextAttack = CreateAttack(MANGLE_A);
					}
				}
			}
		}
		else
		{
			if(!buffs[ROAR_B].active)
			{
				if(comboPoints > 0)
					nextAttack = CreateAttack(ROAR_A);
				else
					nextAttack = CreateAttack(MANGLE_A);
			}
			else
			{
				if(comboPoints == 5)
				{
					if(!buffs[RIP_B].active)
						nextAttack = CreateAttack(RIP_A);
					else if(CanBite())
						nextAttack = CreateAttack(BITE);
				}
				
				if(nextAttack.name == -1)
				{
					if(comboPoints >= minRoarPoints && lowestBuff == ROAR_B && buffs[ROAR_B].timeLeft <= maxRoarClip)
						nextAttack = CreateAttack(ROAR_A);
					else if(!buffs[MANGLE_B].active)
						nextAttack = CreateAttack(MANGLE_A);
					else if(!buffs[RAKE_B].active)
						nextAttack = CreateAttack(RAKE_A);
					else
					{
						if(useShred)
							nextAttack = CreateAttack(SHRED);
						else
							nextAttack = CreateAttack(MANGLE_A);
					}
				}
			}
		}
	}
}

inline UpcomingAttack CreateAttack(AttackName attackName)
{
	UpcomingAttack attack;
	
	attack.name = attackName;
	attack.time = GetAttackTime(attacks[attackName]);
	
	return attack;
}

inline UpcomingAttack CreateAttack(AttackName attackName, int64 targetTime)
{
	UpcomingAttack attack;
	
	attack.name = attackName;
	attack.time = GetAttackTime(attacks[attackName]);
	
	if(attack.time <= targetTime)
		attack.time = targetTime + latency;
	
	return attack;
}



// Returns the earliest time 'attack' can be performed, taking energy, latency, and the GCD into account
inline int64 GetAttackTime(Attack attack)
{
	int64 attackTime;
	
	if(omenActive)
		attackTime = currentTime;
	else
	{
		if(!buffs[BERSERK_B].active)
			attackTime = currentTime + TimeUntilEnergy(attack.cost);
		else
			attackTime = currentTime + TimeUntilEnergy(attack.cost / 2);
	}
	
	if(attackTime < lastGCDAttackTime + gcd)
		attackTime = lastGCDAttackTime + gcd;
	
	return attackTime + latency;
}

// Prints the results of the simulation
void PrintReport(int timeTaken, float fightDamage[])
{
	double total[numAttacks];
	double totalDamage = 0;
	
	for(int i = 0; i < numAttacks; i++)
	{
		total[i] = attacks[i].hits + attacks[i].glances + attacks[i].crits + attacks[i].misses;
		totalDamage += attacks[i].damageDealt;
	}
	
	double avgDPS = totalDamage / (fightLength / PRECISION * numFights);
	double stdDeviation = 0;
	
	for(int i = 1; i <= numFights; i++)
	{
		fightDamage[i] /= (fightLength / PRECISION);
		stdDeviation += pow(fightDamage[i] - avgDPS, 2);
	}
	
	stdDeviation = sqrt(stdDeviation / numFights);
	
	cout << setiosflags(ios::fixed) << setprecision(2);
	
	cout << "Miss chance: " << (1 - currentStats.GetHitChance()) * 100 << "%, Crit chance: " << currentStats.GetCritChance() * 100 << "%, arPen: " << currentStats.GetGearArPenPercent() * 100 << "%, haste: " << ((1 + currentStats.GetStat(HASTERATING) / currentStats.ratingsConstants.ratingPerHaste) * currentStats.GetModifier(HASTEMOD) - 1) * 100 << "%, agi: " << currentStats.GetStat(AGI) << ", str: " << currentStats.GetStat(STR) << ", AP: " << currentStats.GetStat(AP)  << endl << endl;
	
	for(int i = 0; i < numAttacks - 3; i++)
	{
		if(i != ROAR_A)
		{
			if(i == RIP_A)
				cout << "Average " << names[i] << ": " << attacks[i].damageDealt / total[i] << " - " << attacks[i].damageDealt / totalDamage * 100 << "% Crit: " << double(attacks[i].tickCrits) / double(attacks[i].tickHits + attacks[i].tickCrits) * 100 << "% Miss: " << double(attacks[i].misses) / double(total[i]) * 100 << "%" << endl;
			else
				cout << "Average " << names[i] << ": " << attacks[i].damageDealt / total[i] << " - " << attacks[i].damageDealt / totalDamage * 100 << "% Crit: " << double(attacks[i].crits) / double(total[i]) * 100 << "% Miss: " << double(attacks[i].misses) / double(total[i]) * 100 << "%" << endl;
		}
	}
	
	cout << setprecision(3) << "Trinket overlap: " << double(trinketOverlapTime) / (double(buffs[TRINKET1].upTime + buffs[TRINKET2].upTime) / 2.0) * 100 << "%" << endl << endl;
	
	cout << "Rip downtime: " << buffs[RIP_B].downTime / double(numFights * fightLength) * 100 << "%" << endl;
	cout << "Rake downtime: " << buffs[RAKE_B].downTime / double(numFights * fightLength) * 100 << "%" << endl;
	cout << "Mangle downtime: " << buffs[MANGLE_B].downTime / double(numFights * fightLength) * 100 << "%" << endl;
	cout << "Roar downtime: " << buffs[ROAR_B].downTime / double(numFights * fightLength) * 100 << "%" << endl;
	
	cout << endl << "Total damage dealt: " << totalDamage << ", DPS: " << avgDPS << endl;
	cout << "Standard deviation: " << stdDeviation << " (" << stdDeviation / avgDPS * 100 << "%)" << endl;
	
	cout << "Total time taken: " << timeTaken << " seconds" << endl;
}

// (Re)calculates all stats based on all talents, buffs, and gear
inline void CalculateStats()
{
	currentStats = staticStats;
	
	for(int i = 0; i < numActiveBuffs; i++)
		currentStats += buffs[activeBuffs[i]].stats;
}

// Calculates the average damage of each attack based on current stats
inline void CalculateAverageDamage()
{
	double baseSwing = (currentStats.GetStat(AP) / 14 + 55 + currentStats.GetStat(WEAPONDAMAGE));
	attacks[SWING].avgDamage =  baseSwing * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	attacks[SHRED].avgDamage = (baseSwing * 2.25 + 666 + 203 * shredIdol) * currentStats.GetModifier(BLEEDDAMAGEMOD) * (1 + isBleeding * talents.rendAndTear * 4 * .01) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	attacks[MANGLE_A].avgDamage = (baseSwing * 2 + 566) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	attacks[RAKE_A].avgDamage = (currentStats.GetStat(AP) * .01 + 176) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(BLEEDDAMAGEMOD) * currentStats.GetModifier(DAMAGEMOD);
	attacks[RAKE_A].avgTick = (currentStats.GetStat(AP) * .06 + 358) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(DAMAGEMOD);
	attacks[RIP_A].perCP = (currentStats.GetStat(AP) * .01 + 93 + 21 * ripIdol) * currentStats.GetModifier(DAMAGEMOD);
	attacks[RIP_A].staticDamage = 36 * currentStats.GetModifier(DAMAGEMOD);
	attacks[BITE].perCP = (currentStats.GetStat(AP) * .07 + 290) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	attacks[BITE].staticDamage = 190 * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	attacks[BITE].extraEnergyDamage = (currentStats.GetStat(AP) / 410.0 + 9.4) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
	
	// Old pre-3.2 numbers
// 	attacks[SHRED].avgDamage = (baseSwing * 2.25 + 742.5 + 203 * shredIdol) * currentStats.GetModifier(BLEEDDAMAGEMOD) * (1 + isBleeding * talents.rendAndTear * 4 * .01) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
// 	attacks[MANGLE_A].avgDamage = (baseSwing * 2 + 634) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(DAMAGEMOD) * (1 - currentStats.GetArmorDamageReduction());
// 	attacks[RAKE_A].avgDamage = (currentStats.GetStat(AP) * .01 + 190) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(BLEEDDAMAGEMOD) * currentStats.GetModifier(DAMAGEMOD);
// 	attacks[RAKE_A].avgTick = (currentStats.GetStat(AP) * .06 + 387) * (1 + talents.savageFury * 10 * .01) * currentStats.GetModifier(DAMAGEMOD);
// 	attacks[RIP_A].perCP = (currentStats.GetStat(AP) * .01 + 99 + 21 * ripIdol) * currentStats.GetModifier(DAMAGEMOD);
// 	attacks[RIP_A].staticDamage = 39 * currentStats.GetModifier(DAMAGEMOD);
}

inline void DoAttack(AttackName attack, int isFinisher, int isDebuff, int roll, double damage, MTRand_closed &rand)
{
	int cost = attacks[attack].cost;
	
	if(!omenActive)
	{
		if(buffs[BERSERK_B].active)
			cost = attacks[attack].cost / 2;
	}
	else
	{
		cost = 0;
		omenActive = 0;
	}
	
	if(attack == BITE)
	{
		cost += maxBiteExtraEnergy;
		
		if(energy - cost < 0)
			cost = energy;
	}
	
	if(roll == MISS)
	{
		double missMod;
		
		if(isFinisher)
			missMod = 1 - talents.primalPrecision * .4;
		else
			missMod = .2;
		
		attacks[attack].misses++;
		
		energy -= cost * missMod;
		
		#ifdef DEBUG
		cout << names[attack] << " misses" << " at " << currentTime / double(PRECISION) << ", energy: " << energy << ", comboPoints: " << comboPoints << endl;
		#endif
	}
	else if(roll == CRIT)
	{
		attacks[attack].crits++;
		attacks[attack].damageDealt += damage * critMod;
		
		if(!isFinisher)
		{
			comboPoints += 1 + talents.primalFury / 2;
			
			if(comboPoints > 5)
				comboPoints = 5;
		}
		else
			comboPoints = 0;
		
		energy -= cost;
		
		CheckProcs(YELLOW, CRIT, attack, rand);
		
		#ifdef DEBUG
		cout << names[attack] << " crits for " << damage * critMod << " at " << currentTime / double(PRECISION) << ", energy: " << energy << ", comboPoints: " << comboPoints << endl;
		#endif
	}
	else
	{
		attacks[attack].hits++;
		attacks[attack].damageDealt += damage;
		
		if(!isFinisher)
		{
			comboPoints++;
			
			if(comboPoints > 5)
				comboPoints = 5;
		}
		else if(attack == RIP_A)
		{
			buffs[RIP_B].avgTick = attacks[RIP_A].perCP * comboPoints + attacks[RIP_A].staticDamage;
			comboPoints = 0;
		}
		else
			comboPoints = 0;
		
		energy -= cost;
		
		CheckProcs(YELLOW, HIT, attack, rand);
		
		#ifdef DEBUG
		cout << names[attack] << " hits for " << damage << " at " << currentTime / double(PRECISION) << ", energy: " << energy << ", comboPoints: " << comboPoints << endl;
		#endif
	}
	
	if(isDebuff && roll != MISS)
	{
		if(!buffs[attack].active)
		{
			buffs[attack].downTime += currentTime - buffs[attack].lastFaded;
			buffs[attack].active = 1;
			
			activeBuffs[numActiveBuffs] = attack;
			numActiveBuffs++;
			
			if(attack == MANGLE_A)
			{
				currentStats += buffs[MANGLE_B].stats;
				CalculateAverageDamage();
			}
			
			#ifdef DEBUG
			cout << (currentTime - buffs[attack].lastFaded) / double(PRECISION) << " downtime added for " << names[attack] << endl;
			#endif
		}
		
		buffs[attack].lastTrigger = currentTime;
		buffs[attack].endTime = currentTime + buffs[attack].duration;
		buffs[attack].lastTick = currentTime;
		buffs[attack].nextTick = currentTime + buffs[attack].tickFrequency;
		if(attack == RAKE_A)
			buffs[RAKE_B].avgTick = attacks[RAKE_A].avgTick;
	}
	
	nextAttack.name = -1;
	nextAttack.time = fightLength;
	lastGCDAttackTime = currentTime;
}

// Instantly performs the queued up attack
inline void DoNextAttack(MTRand_closed &rand)
{
	switch(nextAttack.name)
	{
		case SHRED:
		{
			double roll = rand();
			
			if(roll <= 1 - currentStats.GetHitChance()) // Attack misses
				DoAttack(SHRED, 0, 0, MISS, 0, rand);
			else
			{
				roll = rand();
				
				if(roll <= currentStats.GetCritChance()) // Attack crits
					DoAttack(SHRED, 0, 0, CRIT, attacks[SHRED].avgDamage, rand);
				else	// Attack hits
					DoAttack(SHRED, 0, 0, HIT, attacks[SHRED].avgDamage, rand);
				
				if(buffs[RIP_B].active && shredGlyph && shredGlyphCounter < 3)
				{
					buffs[RIP_B].lastTrigger += 2 * PRECISION;
					buffs[RIP_B].endTime += 2 * PRECISION;
					shredGlyphCounter++;
					
					#ifdef DEBUG
					cout << "Rip extended by 2 seconds" << endl;
					#endif
				}
			}
		}
		
		break;
		
		case RAKE_A:
		{
			double roll = rand();
			
			if(roll <= 1 - currentStats.GetHitChance()) // Attack misses
				DoAttack(RAKE_A, 0, 1, MISS, 0, rand);
			else
			{
				roll = rand();
				
				if(roll <= currentStats.GetCritChance()) // Attack crits
					DoAttack(RAKE_A, 0, 1, CRIT, attacks[RAKE_A].avgDamage, rand);
				else	// Attack hits
					DoAttack(RAKE_A, 0, 1, HIT, attacks[RAKE_A].avgDamage, rand);
				
				if(!isBleeding)
				{
					isBleeding = 1;
					CalculateAverageDamage();
				}
			}
		}
		
		break;
			
		case MANGLE_A:
		{
			double roll = rand();
			
			if(roll <= 1 - currentStats.GetHitChance()) // Attack misses
				DoAttack(MANGLE_A, 0, 1, MISS, 0, rand);
			else
			{
				roll = rand();
				
				if(roll > 1 - currentStats.GetHitChance() && roll <= 1 - currentStats.GetHitChance() + currentStats.GetCritChance()) // Attack crits
					DoAttack(MANGLE_A, 0, 1, CRIT, attacks[MANGLE_A].avgDamage, rand);
				else	// Attack hits
					DoAttack(MANGLE_A, 0, 1, HIT, attacks[MANGLE_A].avgDamage, rand);
			}
		}
		
		break;
			
		case ROAR_A:
		{
			attacks[ROAR_A].hits++;
			
			if(!buffs[ROAR_B].active)
			{
				buffs[ROAR_B].downTime += currentTime - buffs[ROAR_B].lastFaded;
				buffs[ROAR_B].active = 1;
				
				activeBuffs[numActiveBuffs] = ROAR_B;
				numActiveBuffs++;
				
				currentStats += buffs[ROAR_B].stats;
				CalculateAverageDamage();
				
				#ifdef DEBUG
				cout << (currentTime - buffs[ROAR_B].lastFaded) / double(PRECISION) << " downtime added for Roar" << endl;
				#endif
			}
			
			buffs[ROAR_B].lastTrigger = currentTime;
			buffs[ROAR_B].duration = (9 + comboPoints * 5 + fourT8 * 8) * PRECISION;
			buffs[ROAR_B].endTime = currentTime + buffs[ROAR_B].duration;
			
			comboPoints = 0;
			
			if(!buffs[BERSERK_B].active)
				energy -= attacks[ROAR_A].cost;
			else
				energy -= attacks[ROAR_A].cost / 2;
			
			nextAttack.name = -1;
			nextAttack.time = fightLength;
			lastGCDAttackTime = currentTime;
			
			#ifdef DEBUG
			cout << "Roar used at " << currentTime / double(PRECISION) << ", energy: " << energy << ", comboPoints: " << comboPoints << endl;
			#endif
		}
		
		break;
			
		case RIP_A:
		{
			double roll = rand();
			
			if(roll <= 1 - currentStats.GetHitChance())
			{
				DoAttack(RIP_A, 1, 1, MISS, 0, rand);
			}
			else
			{
				DoAttack(RIP_A, 1, 1, HIT, 0, rand);
				
				if(!isBleeding)
				{
					isBleeding = 1;
					CalculateAverageDamage();
				}
			}
		}
		
		break;
			
		case BITE:
		{
			double roll = rand();
			
			if(roll <= 1 - currentStats.GetHitChance()) // Attack misses
			{
				DoAttack(BITE, 1, 0, MISS, 0, rand);
			}
			else 
			{
				roll = rand();
				
				if(roll <= currentStats.GetCritChance() + talents.rendAndTear * .05 + fourT9 * .05) // Attack crits
				{
					int damage;
					
					if(energy <= attacks[BITE].cost * !omenActive / (1 + buffs[BERSERK_B].active) + maxBiteExtraEnergy)
						damage = (attacks[BITE].staticDamage + attacks[BITE].perCP * comboPoints + attacks[BITE].extraEnergyDamage * (energy - attacks[BITE].cost * !omenActive / (1 + buffs[BERSERK_B].active))) * (1 + .03 * talents.feralAggression);
					else
						damage = (attacks[BITE].staticDamage + attacks[BITE].perCP * comboPoints + attacks[BITE].extraEnergyDamage * maxBiteExtraEnergy) * (1 + .03 * talents.feralAggression);
					
					DoAttack(BITE, 1, 0, CRIT, damage, rand);
				}
				else	// Attack hits
				{
					int damage;
					
					if(energy <= attacks[BITE].cost * !omenActive / (1 + buffs[BERSERK_B].active) + maxBiteExtraEnergy)
						damage = (attacks[BITE].staticDamage + attacks[BITE].perCP * comboPoints + attacks[BITE].extraEnergyDamage * (energy - attacks[BITE].cost * !omenActive / (1 + buffs[BERSERK_B].active))) * (1 + .03 * talents.feralAggression);
					else
						damage = (attacks[BITE].staticDamage + attacks[BITE].perCP * comboPoints + attacks[BITE].extraEnergyDamage * maxBiteExtraEnergy) * (1 + .03 * talents.feralAggression);
					
					DoAttack(BITE, 1, 0, HIT, damage, rand);
				}
			}
		}
		
		break;
			
		case BERSERK_A:
		{
			buffs[BERSERK_B].active = 1;
			buffs[BERSERK_B].lastTrigger = currentTime;
			buffs[BERSERK_B].endTime = currentTime + buffs[BERSERK_B].duration;
			buffs[BERSERK_B].nextCooldown = currentTime + buffs[BERSERK_B].cooldown;
			
			activeBuffs[numActiveBuffs] = BERSERK_B;
			numActiveBuffs++;
			
			if(buffs[FURY_B].active)
			{
				for(int i = 0; i < numActiveBuffs; i++)
				{
					if(activeBuffs[i] == FURY_B)
					{
						buffs[FURY_B].active = 0;
						buffs[FURY_B].lastFaded = currentTime;
						
						currentStats.SubtractStat(WEAPONDAMAGE, buffs[FURY_B].stats.GetStat(WEAPONDAMAGE));
						
						for(int j = i; j < numActiveBuffs - 1; j++)
							activeBuffs[j] = activeBuffs[j + 1];
						
						numActiveBuffs--;
					}
				}
			}
			
			nextAttack.name = -1;
			nextAttack.time = fightLength;
			lastGCDAttackTime = currentTime + 0.5 * PRECISION; // Berserk triggers a 1.5 second GCD
			
			#ifdef DEBUG
			cout << "Berserk used at " << currentTime / double(PRECISION) << ", energy at " << energy << endl;
			#endif
		}
		
		break;
			
		case FFF_A:
		{
			nextAttack.name = -1;
			nextAttack.time = fightLength;
			lastGCDAttackTime = currentTime;
		}
		
		break;
	}
	
	if(energy <= 28 && currentTime >= buffs[FURY_B].nextCooldown && !buffs[BERSERK_B].active)
	{
		buffs[FURY_B].active = 1;
		buffs[FURY_B].lastTrigger = currentTime;
		buffs[FURY_B].endTime = currentTime + buffs[FURY_B].duration;
		buffs[FURY_B].nextCooldown = currentTime + buffs[FURY_B].cooldown;
		nextAttack.name = -1;
		nextAttack.time = fightLength;
		
		activeBuffs[numActiveBuffs] = FURY_B;
		numActiveBuffs++;
		
		energy += talents.kingOfTheJungle * 20;
		
		currentStats.AddStat(WEAPONDAMAGE, buffs[FURY_B].stats.GetStat(WEAPONDAMAGE));
		CalculateAverageDamage();
		
		#ifdef DEBUG
		cout << "Tiger's Fury used at " << currentTime / double(PRECISION) << ", energy: " << energy << ", comboPoints: " << comboPoints << endl;
		#endif
	}
	
	for(int i = TRINKET1; i <= TRINKET2; i++)
	{
		if(buffs[i].type == USE && currentTime >= buffs[i].nextCooldown)
		{
			buffs[i].active = 1;
			buffs[i].lastTrigger = currentTime;
			buffs[i].endTime = currentTime + buffs[i].duration;
			buffs[i].nextCooldown = currentTime + buffs[i].cooldown;
			
			activeBuffs[numActiveBuffs] = i;
			numActiveBuffs++;
			
			currentStats += buffs[i].stats;
			CalculateAverageDamage();
			
			#ifdef DEBUG
			cout << "Trinket " << i << " used at " << currentTime / double(PRECISION) << endl;
			#endif
		}
	}
	
	ChooseNextAttack();
}

// Returns the time the next buff will expire
inline int64 GetLowestBuff()
{
	int64 lowestTime = fightLength;
	
	for(int i = 0; i < numActiveBuffs; i++)
	{
		if(buffs[activeBuffs[i]].canTick && lowestTime > buffs[activeBuffs[i]].nextTick)
			lowestTime = buffs[activeBuffs[i]].nextTick;
		
		if(lowestTime > buffs[activeBuffs[i]].endTime)
			lowestTime = buffs[activeBuffs[i]].endTime;
	}
	
	return lowestTime;
}

// Disables buffs that have expired
inline void CheckBuffDurations()
{
	for(int i = 0; i < numActiveBuffs; i++)
	{
		if(currentTime >= buffs[activeBuffs[i]].endTime)
		{
			int tempBuff = activeBuffs[i];
			
			buffs[tempBuff].active = 0;
			buffs[tempBuff].lastFaded = currentTime;
			
			for(int j = i; j < numActiveBuffs - 1; j++)
				activeBuffs[j] = activeBuffs[j + 1];
			
			numActiveBuffs--;
			
			if(!buffs[RAKE_B].active && !buffs[RIP_B].active && !bleedBot)
				isBleeding = 0;
			
			if(tempBuff == RIP_B)
				shredGlyphCounter = 0;
			
			nextAttack.name = -1;
			
			currentStats -= buffs[tempBuff].stats;
			CalculateAverageDamage();
			ChooseNextAttack();
			
			#ifdef DEBUG
			cout << "Buff " << names[tempBuff] << " ending at " << currentTime / double(PRECISION) << ", damage mod at " << currentStats.GetModifier(DAMAGEMOD) << ", lastFaded: " << buffs[tempBuff].lastFaded << endl;
			#endif
		}
	}
}

// Performs an auto attack
inline void Swing(MTRand_closed &rand)
{
	double roll = rand();
	
	if(roll <= 1 - currentStats.GetHitChance()) // Attack misses
	{
		attacks[SWING].misses++;
		
		#ifdef SWING_DEBUG
		cout << "Swing misses at " << currentTime / double(PRECISION) << endl;
		#endif
	}
	else if(roll > 1 - currentStats.GetHitChance() && roll <= 1 - currentStats.GetHitChance() + glanceChance) // Attack glances
	{
		attacks[SWING].glances++;
		attacks[SWING].damageDealt += attacks[SWING].avgDamage * glanceMod;
		
		CheckProcs(WHITE, HIT, -1, rand);
		
		#ifdef SWING_DEBUG
		cout << "Swing glances for " << attacks[SWING].avgDamage * glanceMod << " at " << currentTime / double(PRECISION) << endl;
		#endif
	}
	else if(roll > 1 - currentStats.GetHitChance() + glanceChance && roll <= 1 - currentStats.GetHitChance() + glanceChance + currentStats.GetCritChance()) // Attack crits
	{
		attacks[SWING].crits++;
		attacks[SWING].damageDealt += attacks[SWING].avgDamage * critMod;
		
		CheckProcs(WHITE, CRIT, -1, rand);
		
		#ifdef SWING_DEBUG
		cout << "Swing crits for " << attacks[SWING].avgDamage * critMod << " at " << currentTime / double(PRECISION) << endl;
		#endif
	}
	else	// Attack hits
	{
		attacks[SWING].hits++;
		attacks[SWING].damageDealt += attacks[SWING].avgDamage;
		
		CheckProcs(WHITE, HIT, -1, rand);
		
		#ifdef SWING_DEBUG
		cout << "Swing hits for " << attacks[SWING].avgDamage << " at " << currentTime / double(PRECISION) << endl;
		#endif
	}
}

// Checks whether an attack should trigger a proc based on the type of attack, whether it crits, and a random roll
inline void CheckProcs(AttackType attackType, int crit, int attack, MTRand_closed &rand)
{
	double roll;
	
	for(int i = TRINKET1; i <= WEAPONENCHANT; i++)
	{
		if(currentTime >= buffs[i].nextCooldown && ((attackType != TICK && (buffs[i].trigger == HIT || crit)) || buffs[i].trigger == DAMAGE) && buffs[i].type == PROC && buffs[i].enabled)
		{
			roll = rand();
			if(roll <= buffs[i].procChance)
			{
				buffs[i].lastTrigger = currentTime;
				buffs[i].endTime = currentTime + buffs[i].duration;
				buffs[i].nextCooldown = currentTime + buffs[i].cooldown;
				
				lowestBuffTime = GetLowestBuff();
				
				if(!buffs[i].active)
				{
					buffs[i].active = 1;
					
					activeBuffs[numActiveBuffs] = i;
					numActiveBuffs++;
					
					currentStats += buffs[i].stats;
					CalculateAverageDamage();
					
					if(buffs[TRINKET1].active && buffs[TRINKET2].active)
					{
						if(buffs[TRINKET1].endTime > buffs[TRINKET2].endTime)
							trinketOverlapTime += buffs[TRINKET2].endTime - currentTime;
						else
							trinketOverlapTime += buffs[TRINKET1].endTime - currentTime;
					}
					
					if(i == TRINKET1 || i == TRINKET2)
						buffs[i].upTime += buffs[i].duration;
				}
				
				#ifdef DEBUG
				cout << "Proc " << names[i] << " at " << currentTime / double(PRECISION) << endl;
				#endif
			}
		}
	}
	
	if((attackType == WHITE && talents.omenOfClarity) || (attackType == TICK && twoT8))
	{
		roll = rand();
		
		if((attackType == WHITE && roll <= omenProcChance) || (attackType == TICK && roll <= twoT8ProcChance))
		{
			omenActive = 1;
			nextAttack.name = -1;
			nextAttack.time = 0 - fightLength;
			
			ChooseNextAttack();
			
			#ifdef DEBUG
			cout << "Omen proc at " << currentTime / double(PRECISION) << endl;
			#endif
		}
	}
	
	if(mangleIdol && attack == MANGLE_A)
	{
		if(!buffs[MANGLEIDOL].active)
		{
			buffs[MANGLEIDOL].active = 1;
			
			activeBuffs[numActiveBuffs] = MANGLEIDOL;
			numActiveBuffs++;
			
			currentStats += buffs[MANGLEIDOL].stats;
			CalculateAverageDamage();
		}
		
		buffs[MANGLEIDOL].lastTrigger = currentTime;
		buffs[MANGLEIDOL].endTime = currentTime + buffs[MANGLEIDOL].duration;
		
		#ifdef DEBUG
		cout << "Proc Mangle Idol at " << currentTime / double(PRECISION) << endl;
		#endif
	}
	
	if(mutilationIdol && (attack == MANGLE_A || attack == SHRED))
	{
		roll = rand();
		
		if(roll <= mutilationIdolProcChance)
		{
			if(!buffs[MUTILATIONIDOL].active)
			{
				buffs[MUTILATIONIDOL].active = 1;
				
				activeBuffs[numActiveBuffs] = MUTILATIONIDOL;
				numActiveBuffs++;
				
				currentStats += buffs[MUTILATIONIDOL].stats;
				CalculateAverageDamage();
			}
			
			buffs[MUTILATIONIDOL].lastTrigger = currentTime;
			buffs[MUTILATIONIDOL].endTime = currentTime + buffs[MANGLEIDOL].duration;
			
			#ifdef DEBUG
			cout << "Proc Mutilation Idol at " << currentTime / double(PRECISION) << endl;
			#endif
		}
	}
}

// Evaluates current conditions and decides whether Ferocious Bite is appropriate
// Returns 0 or 1
inline bool CanBite()
{
	int timeToRoar;
	int timeToRip;
	int tempEnergy = energy - (attacks[BITE].cost * !omenActive * (1.0 / (1 + buffs[BERSERK_B].active))) - 30;
	int furyTimeLeft = buffs[FURY_B].nextCooldown - currentTime;
	
	if(tempEnergy < 0)
		tempEnergy = 0;
	
	if(furyTimeLeft < 0)
		furyTimeLeft = 0;
	
	timeToRoar = (minRoarPoints / (1 + currentStats.GetCritChance()) * 42 - tempEnergy) * energyTickRate;
	timeToRip = (5 / (1 + currentStats.GetCritChance()) * 42 - tempEnergy) * energyTickRate;
	
	if(timeToRoar < timeToRip)
		timeToRip += timeToRoar;
	
	if(furyTimeLeft < timeToRip + timeToRoar)
		timeToRip -= 20 * talents.kingOfTheJungle * energyTickRate;
	
	if(buffs[RIP_B].timeLeft + 4 * PRECISION >= timeToRip && energy <= 40 * (1.0 / (1 + buffs[BERSERK_B].active)))
		return 1;
	
	return 0;
}
